package db

import (
	"context"
	"fmt"
	"newsletter-subs/pkg/config"
	"newsletter-subs/pkg/util"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
)

var RedisClinet *redis.Client

type RedisLock struct {
	redisClient   *redis.Client
	wait          bool // 是否等待
	lockKey       string
	lockValue     string
	timeout       time.Duration
	waitTimeout   time.Duration
	retryInterval time.Duration
}

func InitRedis() {
	RedisClinet = newClient()
	defer RedisClinet.Close()

	util.Logger.Println("Redis connected")

	// 设置key
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
	defer cancel()
	err := RedisClinet.Set(ctx, "name", "john", 1*time.Hour).Err()
	if err != nil {
		panic(err)
	}

	// 获取key
	val, err := RedisClinet.Get(ctx, "name").Result()
	if err != nil {
		panic(err)
	}
	fmt.Println("redis get name", val)
}

// 创建redis客户端
func newClient() *redis.Client {
	client := redis.NewClient(&redis.Options{
		Addr:     fmt.Sprintf("%s:%s", config.ConfigVar.Redis.Url, config.ConfigVar.Redis.Port),
		Password: "",
		DB:       0,
	})
	return client
}

func NewRedisLock(redisClient *redis.Client, lockKey string) *RedisLock {
	return &RedisLock{
		redisClient:   redisClient,
		wait:          true,
		lockKey:       fmt.Sprintf("s:lock:%s", lockKey),
		timeout:       time.Second * 5,
		waitTimeout:   time.Second * 5,
		retryInterval: time.Millisecond * 100,
	}
}

func (rl *RedisLock) SetWait(wait bool) *RedisLock {
	rl.wait = wait
	return rl
}

func (rl *RedisLock) SetTimeout(timeout time.Duration) *RedisLock {
	rl.timeout = timeout
	return rl
}

func (rl *RedisLock) SetWaitTimeout(waitTimeout time.Duration) *RedisLock {
	rl.waitTimeout = waitTimeout
	return rl
}

func (rl *RedisLock) SetRetryInterval(retryInterval time.Duration) *RedisLock {
	rl.retryInterval = retryInterval
	return rl
}

func (rl *RedisLock) Lock() (bool, error) {
	// 生成随机 UUID 作为锁的值
	rl.lockValue = uuid.New().String()
	ctx, cancel := context.WithTimeout(context.Background(), rl.waitTimeout)
	defer cancel()

	// 循环尝试获取锁
	for {
		select {
		case <-ctx.Done():
			return false, nil
		default:
			success, err := rl.redisClient.SetNX(ctx, rl.lockKey, rl.lockValue, rl.timeout).Result()
			if err != nil || (!success && !rl.wait) {
				return false, err
			}
			if success {
				return true, nil
			}
			time.Sleep(rl.retryInterval)
		}
	}
}

func (rl *RedisLock) Unlock() (bool, error) {
	script := "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	result, err := rl.redisClient.Eval(ctx, script, []string{rl.lockKey}, rl.lockValue).Int()
	if err != nil {
		return false, err
	}
	return result > 0, nil
}
